package mtime

import (
	"bytes"
	"fmt"
	"gopkg.in/mgo.v2"
	"io/ioutil"
	"net/http"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/PuerkitoBio/goquery"
	"github.com/astaxie/beego/logs"
	"github.com/robertkrimen/otto"
	"gopkg.in/mgo.v2/bson"
	"gopkg.in/redis.v4"

	"imovie_collect/lib/mdb"
	"imovie_collect/models"
	"imovie_collect/utils"
)

var DefaultClient *http.Client

func init() {
	DefaultClient = &http.Client{
		Timeout: time.Second * 10,
	}
}

type Mtime struct {
	start int
	end   int
	mq    *redis.Client
	sess  *mdb.MdbSession
}

func NewMtime(start, end int, mq *redis.Client, sess *mdb.MdbSession) *Mtime {
	return &Mtime{
		start: start,
		end:   end,
		mq:    mq,
		sess:  sess,
	}
}

func (m *Mtime) Run(mode string) {
	movieUrlChan := make(chan string, 100)
	wg := new(sync.WaitGroup)
	switch mode {
	case "alone":
		wg.Add(1)
		go func() {
			m.Pub(m.start, m.end, movieUrlChan)
			wg.Done()
		}()
		m.Sub(movieUrlChan)
	case "pub":
		wg.Add(1)
		go func() {
			m.Sub4Mq(movieUrlChan)
			wg.Done()
		}()
		m.Pub(m.start, m.end, movieUrlChan)
	case "sub":
		wg.Add(1)
		go func() {
			m.PubByMq(movieUrlChan)
			wg.Done()
		}()
		m.Sub(movieUrlChan)
	case "fill":
		wg.Add(1)
		go func() {
			m.Sub4Mq(movieUrlChan)
			wg.Done()
		}()
		m.PubByMgo(movieUrlChan)
	}
	close(movieUrlChan)
	wg.Wait()
}

func (m *Mtime) PubByMq(movieUrlChan chan<- string) {
	for true {
		stringSliceCmd := m.mq.BRPop(time.Second*3, "movie_url")
		for _, movieUrl := range stringSliceCmd.Val() {
			movieUrlChan <- movieUrl
		}
	}
}

func (m *Mtime) PubByMgo(movieUrlChan chan<- string) {
	movies := make([]*models.Movie, 0, 100)
	m.sess.WithC("movie_detail", func(col *mgo.Collection) error {
		return col.Find(bson.M{"actors": bson.M{"$exists": false}}).Select(bson.M{"_id": 1, "name": 1}).All(&movies)
	})

	for _, movie := range movies {
		logs.Debug("fill %s", movie.Name)
		movieUrlChan <- fmt.Sprintf("http://movie.mtime.com/%s/", movie.Id)
	}
}

func (m *Mtime) Pub(start, end int, movieUrlChan chan<- string) {
	now := time.Now()
	if end == 0 {
		end = now.Year()
	}

	if start == 0 {
		start = 1888
	}

	for year := end; year >= start; year-- {
		for !m.getAllByYear(year, movieUrlChan) {
			logs.Notice("try %d year", year)
		}
	}
}

func (m *Mtime) FillPeople() {
	movies := make([]*models.Movie, 0, 100)
	m.sess.WithC("movie_detail", func(col *mgo.Collection) error {
		return col.Find(bson.M{}).All(&movies)
	})

	peopleIdChan := make(chan string, 100)
	wg := new(sync.WaitGroup)
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			m.fillPeople(peopleIdChan)
			wg.Done()
		}()
	}

	for _, movie := range movies {
		logs.Debug("fill movie %s %s people", movie.Id, movie.Name)
		for _, people := range append(movie.Directors, movie.ScreenWriters...) {
			if people.Id != "" && !m.existsPeople(people.Id) {
				logs.Debug("查找人物 %s", people.Name)
				peopleIdChan <- people.Id
			}
		}

		for _, actor := range movie.Actors {
			if actor.Id != "" && !m.existsPeople(actor.Id) {
				logs.Debug("查找人物 %s", actor.Name)
				peopleIdChan <- actor.Id
			}
		}
	}

	close(peopleIdChan)
	wg.Wait()
}

func (m *Mtime) fillPeople(peopleIdChan <-chan string) {
	for peopleId := range peopleIdChan {
		if people, err := m.getPeople(fmt.Sprintf("http://people.mtime.com/%s/", peopleId)); err == nil {
			if err := m.saveItem(people); err != nil {
				logs.Debug("save people err:%s", err.Error())
			}
		}
	}
}

func (m *Mtime) Sub4Mq(movieUrlChan <-chan string) {
	for movieUrl := range movieUrlChan {
		m.mq.LPush("movie_url", movieUrl)
	}
}

func (m *Mtime) Sub(movieUrlChan <-chan string) {
	wg := new(sync.WaitGroup)
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(movieUrlChan <-chan string) {
			defer wg.Done()
			for movieUrl := range movieUrlChan {
				if movie, err := m.getMtimeItem(movieUrl); err == nil {
					if movie.Name == "" {
						continue
					}

					logs.Debug("get movie %s %s", movie.Id, movie.Name)
					if err := m.saveItem(movie); err != nil {
						logs.Debug("save movie err:%s", err.Error())
					}

					for _, people := range append(movie.Directors, movie.ScreenWriters...) {
						if people.Url != "" && !m.existsPeople(people.Id) {
							logs.Debug("查找人物 %s", people.Name)
							if people, err := m.getPeople(people.Url); err == nil {
								if err := m.saveItem(people); err != nil {
									logs.Debug("save people err:%s", err.Error())
								}
							}
						}
					}

					for _, actor := range movie.Actors {
						if actor.Url != "" && !m.existsPeople(actor.Id) {
							logs.Debug("查找人物 %s", actor.Name)
							if people, err := m.getPeople(actor.Url); err == nil {
								if err := m.saveItem(people); err != nil {
									logs.Debug("save people err:%s", err.Error())
								}
							}
						}
					}
				}
			}
		}(movieUrlChan)
	}

	wg.Wait()
}

func (m *Mtime) saveItem(item models.Model) error {
	return m.sess.Upsert(item.Topic(), bson.M{"_id": item.GetId()}, bson.M{"$set": item})
}

func (m *Mtime) getAllByYear(year int, movieUrlChan chan<- string) bool {
	return m.getListByYear(year, movieUrlChan)
}

func (m *Mtime) getReq(requestUrl string) (resp *http.Response, err error) {
	resp, err = DefaultClient.Get(requestUrl)
	return
}

// 按年获取该年份所有电影
func (m *Mtime) getListByYear(year int, movieUrlChan chan<- string) (ok bool) {
	page := 1
	var count, total int64 = 0, -1
	try := 0
	for true {
		time.Sleep(time.Second * time.Duration(4))
		if try > 3 {
			logs.Notice("尝试超过%d次，退出循环", try)
			break
		}

		queryUrl := fmt.Sprintf("http://service.channel.mtime.com/service/search.mcs?Ajax_CallBack=true&Ajax_CallBackType=Mtime.Channel.Pages.SearchService&Ajax_CallBackMethod=SearchMovieByCategory&Ajax_CrossDomain=1&Ajax_CallBackArgument0=&Ajax_CallBackArgument1=0&Ajax_CallBackArgument2=0&Ajax_CallBackArgument3=0&Ajax_CallBackArgument4=0&Ajax_CallBackArgument5=0&Ajax_CallBackArgument6=0&Ajax_CallBackArgument7=0&Ajax_CallBackArgument8=&Ajax_CallBackArgument9=%d&Ajax_CallBackArgument10=%d&Ajax_CallBackArgument11=&Ajax_CallBackArgument12=&Ajax_CallBackArgument13=0&Ajax_CallBackArgument14=1&Ajax_CallBackArgument15=0&Ajax_CallBackArgument16=1&Ajax_CallBackArgument17=4&Ajax_CallBackArgument18=%d&Ajax_CallBackArgument19=0", year, year, page)
		logs.Debug("搜索年份 %d 第%d页 %s", year, page, queryUrl)
		resp, err := m.getReq(queryUrl)
		if err != nil {
			try += 1
			continue
		}
		try = 0

		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			try += 1
			logs.Warn("%s 读取数据失败 %s", queryUrl, err.Error())
			continue
		}
		try = 0

		vm, _, err := otto.Run(string(data))
		if err != nil {
			logs.Warn("%s 执行js失败", queryUrl)
			break
		}

		if _, err := vm.Run("htmls = searchMovieByCategoryResultObject.value.listHTML; total = searchMovieByCategoryResultObject.value.totalCount"); err != nil {
			time.Sleep(time.Second * 10)
			logs.Warn("%s 解析js失败", queryUrl)
			break
		}

		if total == -1 {
			if value, err := vm.Get("total"); err != nil {
				logs.Warn("%s 获取总数失败", queryUrl)
				break
			} else {
				// 设置总数
				total, err = value.ToInteger()
				if err != nil {
					logs.Warn("%s 获取总数失败", queryUrl)
					break
				}
			}
		}

		value, err := vm.Get("htmls")
		if err != nil {
			logs.Warn("%s 获取htmls失败", queryUrl)
			break
		}

		htmls, err := value.ToString()
		if err != nil {
			logs.Warn("%s 获取htmls失败", queryUrl)
			break
		}

		doc, err := goquery.NewDocumentFromReader(bytes.NewBufferString(htmls))
		if err != nil {
			logs.Warn("%s 解析dom节点失败", queryUrl)
			break
		}

		size := 0
		doc.Find("ul li div.td h3 a").Each(func(index int, sel *goquery.Selection) {
			movieUrl, exists := sel.Attr("href")
			if !exists {
				return
			}

			movieUrlChan <- strings.TrimSpace(movieUrl)
			count++
			size++
		})

		if size == 0 {
			if _, err := vm.Run("isRobot = searchMovieByCategoryResultObject.value.isRobot;"); err == nil {
				value, err := vm.Get("isRobot")
				if err != nil {
					logs.Warn("%s 失败", queryUrl)
					break
				}
				if isRobot, err := value.ToBoolean(); isRobot || err != nil {
					try++
					logs.Notice("起床打码啦")
					continue
				}
			}
			logs.Notice("%d 年 当前收到%d 总数%d, 退出查询", year, count, total)
			ok = true
			break
		}

		if count >= total {
			logs.Notice("%d 年 当前收到%d 总数%d, 退出查询", year, count, total)
			ok = true
			return
		}

		page++
	}
	return
}

func (m *Mtime) getDoc(url string) (doc *goquery.Document, err error) {
	resp, err := m.getReq(url)
	if err != nil {
		return
	}

	doc, err = goquery.NewDocumentFromResponse(resp)
	return
}

func (m *Mtime) getPeople(peopleUrl string) (item *models.People, err error) {
	id, err := m.getIdByUrl(peopleUrl)
	if err != nil {
		return
	}

	doc, err := m.getDoc(peopleUrl)
	if err != nil {
		return
	}

	item = new(models.People)
	item.Id = id
	item.Name = doc.Find("#per_head > div.per_head > div > h2").Text()
	item.EnName = doc.Find("#per_head > div.per_head > div > p.enname").Text()
	titles := strings.TrimSpace(doc.Find("#per_head > div.per_head > div > p.mt9.__r_c_").Text())
	item.Titles = make([]string, 0, 5)
	for _, title := range strings.Split(titles, " | ") {
		item.Titles = append(item.Titles, strings.TrimSpace(title))
	}

	item.Icon, _ = doc.Find("#personDetailRegion > div.per_cover.__r_c_ > span > a > img").Attr("src")
	item.Sign = strings.TrimSpace(doc.Find("#personDetailRegion > dl.per_base_star.clearfix.__r_c_ > dd:nth-child(1) > a > i").Text())
	item.Height = doc.Find("#personDetailRegion > dl.per_base_star.clearfix.__r_c_ > dd:nth-child(2) > span").Text()
	info := doc.Find("#personDetailRegion > dl.per_base_born.__r_c_ > dd").Text()
	item.Birthday = m.getDate(info)
	item.Location = strings.TrimSpace(strings.Replace(info, item.Birthday, "", 1))
	item.Intro = doc.Find("#rightContentDiv > div.per_rmod.per_info.__r_c_ > p:nth-child(1)").Text()

	item.Images = make([]string, 0, 10)
	doc.Find("#rightContentDiv > div.clearfix.per_rmod > dl > dd").Each(func(index int, sel *goquery.Selection) {
		image, _ := sel.Find("img").Attr("src")
		if image != "" {
			item.Images = append(item.Images, image)
		}
	})
	return
}

func (m *Mtime) existsPeople(id string) bool {
	return m.sess.ExistId("people", id)
}

func (m *Mtime) getMtimeItem(movieUrl string) (item *models.Movie, err error) {
	id, err := m.getIdByUrl(movieUrl)
	if err != nil {
		return
	}

	doc, err := m.getDoc(movieUrl)
	if err != nil {
		return
	}

	item = new(models.Movie)
	item.Id = id
	item.Name = strings.TrimSpace(doc.Find("#db_head > div.db_ihead > div > div.clearfix > h1").Text())
	item.Year = doc.Find("#db_head > div.db_ihead > div > div.clearfix > p.db_year > a").Text()
	item.EnName = doc.Find("#db_head > div.db_ihead > div > div.clearfix > p.db_enname").Text()
	item.Duration = doc.Find("#db_head > div.db_ihead > div > div.otherbox.__r_c_ > span").Text()

	types := make([]string, 0, 3)
	doc.Find("#db_head > div.db_ihead > div > div.otherbox.__r_c_ > a[property='v:genre']").Each(func(index int, sel *goquery.Selection) {
		types = append(types, sel.Text())
	})
	item.Types = types
	item.Release = doc.Find("#db_head > div.db_ihead > div > div.otherbox.__r_c_ > a[property='v:initialReleaseDate']").Text()
	item.ReleaseTime = utils.GetTime(item.Release)

	// 上映信息
	baseInfo := doc.Find("#db_head > div.db_ihead > div > div.otherbox.__r_c_").Text()
	baseInfos := strings.Split(baseInfo, item.Release)
	if len(baseInfos) > 0 {
		item.ReleaseLocation = baseInfos[len(baseInfos)-1]
	}

	doc.Find("#movie_warp > div.db_bodyout > div.db_shadow > div.db_contout > div.db_cont > div.clearfix.zoom > div.base_r > div.clearfix.pt15 > dl > dd.__r_c_").Each(func(index int, sel *goquery.Selection) {
		lable := strings.TrimSpace(sel.Find("strong").Text())
		tags := make([]string, 0, 10)
		sel.Find("a").Each(func(index int, sel *goquery.Selection) {
			tags = append(tags, sel.Text())
		})

		for i := 0; i < len(tags); i++ {
			tags[i] = utils.TrimeSpace(tags[i])
		}

		switch lable {
		case "导演：":
			directors := make([]*models.People, 0, 10)
			sel.Find("a").Each(func(index int, sel *goquery.Selection) {
				peopleUrl, _ := sel.Attr("href")
				people := new(models.People)
				people.Id, _ = m.getIdByUrl(peopleUrl)
				if people.Id == "" {
					return
				}
				people.Url = peopleUrl
				people.Name = strings.TrimSpace(sel.Text())
				directors = append(directors, people)
			})
			item.Directors = directors
		case "编剧：":
			screenWriters := make([]*models.People, 0, 10)
			sel.Find("a").Each(func(index int, sel *goquery.Selection) {
				peopleUrl, _ := sel.Attr("href")
				people := new(models.People)
				people.Id, _ = m.getIdByUrl(peopleUrl)
				if people.Id == "" {
					return
				}
				people.Name = strings.TrimSpace(sel.Text())
				people.Url = peopleUrl
				screenWriters = append(screenWriters, people)
			})
			item.ScreenWriters = screenWriters
		case "国家地区：":
			item.Countrys = tags
		case "发行公司：":
			if len(tags) > 0 {
				item.ReleaseCompanys = tags[:len(tags)-1]
			} else {
				item.ReleaseCompanys = tags
			}
		case "更多片名：":
			tags = tags[0:0]
			sel.Find("span").Each(func(index int, sel *goquery.Selection) {
				tag := utils.TrimeSpace(sel.Text())
				tags = append(tags, tag)
			})

			item.Nicknames = tags
		}
	})

	item.Intro = doc.Find("#movie_warp > div.db_bodyout > div.db_shadow > div > div.db_contout > div.db_cont > div.clearfix.zoom > div.base_r > div.clearfix.pt15 > dl > dt > p.mt6.lh18").Text()

	item.Actors = make([]*models.Actor, 0, 10)
	doc.Find("#movie_warp > div.db_bodyout > div.db_shadow > div > div.db_contout > div.db_cont > div.clearfix.zoom > div.base_r > div.clearfix.pt15 > div.info_r dl.main_actor").Each(func(index int, sel *goquery.Selection) {
		actor := new(models.Actor)
		actorUrl, _ := sel.Find("dd > a").Attr("href")
		actor.Id, _ = m.getIdByUrl(actorUrl)
		if actor.Id == "" {
			return
		}
		actor.Url = actorUrl
		actor.Icon, _ = sel.Find("dd > img").Attr("src")
		actor.Name = sel.Find("dd > p:nth-child(2) > a").Text()
		actor.EnName = sel.Find("dd > p:nth-child(3) > a").Text()
		actor.Role = sel.Find("dd > p:nth-child(4)").Text()
		item.Actors = append(item.Actors, actor)
	})

	item.Images = make([]string, 0, 10)
	mainImage, exists := doc.Find("#db_head > div.db_coverout > div > div > div a img").Attr("src")
	if exists {
		item.Images = append(item.Images, mainImage)
	}

	item.Score = m.getMtimeScore(item.Id)
	return
}

func (m *Mtime) getMtimeScore(mtimeId string) (score float64) {
	queryUrl := fmt.Sprintf("http://service.library.mtime.com/Movie.api?Ajax_CallBack=true&Ajax_CallBackType=Mtime.Library.Services&Ajax_CallBackMethod=GetMovieOverviewRating&Ajax_CallBackArgument0=%s", mtimeId)
	resp, err := m.getReq(queryUrl)
	if err != nil {
		return
	}

	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	vm, _, err := otto.Run(string(data))
	if err != nil {
		return
	}

	if _, err := vm.Run("rating = movieOverviewRatingResult.value.movieRating.RatingFinal"); err != nil {
		return
	}

	value, err := vm.Get("rating")
	if err != nil {
		return
	}

	score, _ = value.ToFloat()
	return
}

func (m *Mtime) getDate(str string) (date string) {
	dateReg, err := regexp.Compile("[\\d]{4}-[\\d]{1,2}-[\\d]{1,2}")
	if err != nil {
		return
	}

	date = dateReg.FindString(str)
	return
}

func (m *Mtime) getIdByUrl(movieUrl string) (number string, err error) {
	// http://movie.mtime.com/12281/
	numberReg, err := regexp.Compile("[^\\D][\\d]*")
	if err != nil {
		return
	}

	number = numberReg.FindString(movieUrl)
	return
}
